package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/hashicorp/consul/api"
	"github.com/urfave/cli"
)

type clusterMap map[string]string //db => cluster

func (m clusterMap) String() string {
	str := ""
	for k, v := range m {
		str += fmt.Sprintf("%s:%s\n", k, v)
	}
	return str
}

func main() {
	app := cli.NewApp()
	app.Name = "EPGProxy Consul Config"
	app.Usage = "Import and Sync config"
	app.Version = "0.0.1"
	app.Commands = []cli.Command{
		{
			Name:   "sync",
			Usage:  "Sync consul kv to other consul",
			Action: sync2consul,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name: "config-dir,dir", Value: "./import_config/", Usage: "Config Dir",
				},
				cli.StringFlag{
					Name: "sync-config,c", Value: "./consul_sync.json", Usage: "consul sync config",
				},
			},
		},
		{
			Name:   "import",
			Usage:  "Import config to consul",
			Action: import2consul,
			Flags: []cli.Flag{
				cli.StringFlag{
					Name: "config-dir,dir", Value: "./import_config/", Usage: "Config Dir",
				},
				cli.StringFlag{
					Name: "nodes,n", Value: "nodes_db.json", Usage: "nodes config file name",
				},
			},
		},
	}

	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

func sync2consul(cli *cli.Context) error {
	//config
	f := filepath.Join(cli.String("dir"), cli.String("c"))
	conf := make(map[string]consConf)
	err := load(f, &conf)
	if err != nil {
		fmt.Println(err)
		return err
	}
	src := conf["src"]
	dst := conf["dst"]
	if src.Prefix == "" || dst.Prefix == "" {
		return errors.New("src or dst prefix is null")
	}
	if src.Addr == dst.Addr && src.Prefix == dst.Prefix {
		return errors.New("source and dst is the same")
	}

	//consul
	var config = &api.Config{Address: src.Addr, Token: src.Token, Datacenter: src.Datacenter}
	srcCli, err := api.NewClient(config)
	if err != nil {
		fmt.Println(err)
		return err
	}

	config = &api.Config{Address: dst.Addr, Token: dst.Token, Datacenter: dst.Datacenter}
	dstCli, err := api.NewClient(config)
	if err != nil {
		fmt.Println(err)
		return err
	}

	pairs, _, err := srcCli.KV().List(src.Prefix, &api.QueryOptions{})
	if err != nil {
		return err
	}
	for _, v := range pairs {
		//is dir.
		if strings.HasSuffix(v.Key, "/") {
			continue
		}
		newKey := strings.Replace(v.Key, src.Prefix, dst.Prefix, 1)

		fmt.Printf("sync src:%s@%s/%s \t\t==> dst:%s@%s/%s \n", src.Addr, src.Datacenter, v.Key, dst.Addr, dst.Datacenter, newKey)
		_, err := dstCli.KV().Put(&api.KVPair{Key: strings.Replace(v.Key, src.Prefix, dst.Prefix, 1), Value: v.Value}, &api.WriteOptions{})
		if err != nil {
			return err
		}
	}
	fmt.Println("Finish")
	return nil
}

func import2consul(cli *cli.Context) error {

	initConsul(filepath.Join(cli.String("dir"), "consul.json"))
	var err error
	tmplMaster, err = ioutil.ReadFile(filepath.Join(cli.String("dir"), "./template_master.toml"))
	tmplSlave, err = ioutil.ReadFile(filepath.Join(cli.String("dir"), "./template_slave.toml"))
	data := make([]jsonStruct, 0, 64)
	nodeConf := filepath.Join(cli.String("dir"), cli.String("n"))
	err = load(nodeConf, &data)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	nodes := make([]node, 0, 128)
	for _, v := range data {
		_nodes := v.ParseToNodes()
		nodes = append(nodes, _nodes...)
	}

	//check the database name is config right
	databases := clusterMap{}
	var checkOK = true
	for _, v := range nodes {
		for _, _v := range v.Dbs {
			if c, ok := databases[_v]; ok && c != v.Cluster {
				checkOK = false
				fmt.Printf("Error Cluster, db: %s have in %s, and add %s\n", _v, c, v.Cluster)
				continue
			}
			databases[_v] = v.Cluster
		}
	}
	if !checkOK {
		os.Exit(1)
	}

	//import the data to consul
	for _, v := range nodes {
		if err := importToPath(v); err != nil {
			fmt.Println(err)
			continue
		}
		fmt.Printf("OK\t%s\t%s\n", v.Role, v.Name())
	}
	fmt.Println("Finish")
	return nil
}
